﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using DirectShowLib;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private Capture _cam;
        public Form1()
        {
            InitializeComponent();

        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (_cam == null)
            {
                _cam = new Capture(0, 30, 1024, 768, @"C:\a1.avi");
                //_cam = new Capture(0, 30, 320, 240, @"C:\a1.avi");
                _cam.SetLogo(@"C:\logo.png");
                _cam.Start();
                return;
            }
            _cam.Pause();
            _cam.Dispose();
            _cam = null;
        }
    }
    /// <summary> Summary description for MainForm. </summary>
    internal class Capture : ISampleGrabberCB, IDisposable
    {
        #region Member variables

        /// <summary> graph builder interface. </summary>
        private IFilterGraph2 filterGraph = null;
        IMediaControl mediaCtrl = null;

        /// <summary> Set by async routine when it captures an image </summary>
        private bool _isRunning = false;

        /// <summary> Dimensions of the image, calculated once in constructor. </summary>
        private int _videoWidth;
        private int _videoHeight;
        private int _stride;

        BitmapData _bmdLogo = null;
        Bitmap _Bitmap = null;

#if DEBUG
        // Allow you to "Connect to remote graph" from GraphEdit
        DsROTEntry m_rot = null;
#endif

        #endregion

        #region API

        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
        private static extern void CopyMemory(IntPtr Destination, IntPtr Source, [MarshalAs(UnmanagedType.U4)] uint Length);

        #endregion


        /// <summary>
        /// zero based device index, and some device parms, plus the file name to save to
        /// </summary>
        /// <param name="deviceNum"></param>
        /// <param name="frameRate"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="fileName"></param>
        public Capture(int deviceNum, int frameRate, int width, int height, string fileName)
        {
            // Get the collection of video devices
            var capDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);

            if (deviceNum + 1 > capDevices.Length)
                throw new Exception("No video capture devices found at that index!");

            try
            {
                // Set up the capture graph
                SetupGraph(capDevices[deviceNum], frameRate, width, height, fileName);
            }
            catch
            {
                Dispose();
                throw;
            }
        }

        /// <summary> release everything. </summary>
        public void Dispose()
        {
            CloseInterfaces();
            if (_Bitmap == null) return;
            _Bitmap.UnlockBits(_bmdLogo);
            _Bitmap = null;
            _bmdLogo = null;
        }
        // Destructor
        ~Capture()
        {
            CloseInterfaces();
        }


        /// <summary> capture the next image </summary>
        public void Start()
        {
            if (_isRunning) return;
            var hr = mediaCtrl.Run();
            DsError.ThrowExceptionForHR(hr);

            _isRunning = true;
        }
        // Pause the capture graph.
        // Running the graph takes up a lot of resources.  Pause it when it
        // isn't needed.
        public void Pause()
        {
            if (!_isRunning) return;
            var hr = mediaCtrl.Pause();
            DsError.ThrowExceptionForHR(hr);

            _isRunning = false;
        }

        /// <summary> Specify the logo file to write onto each frame </summary>
        public void SetLogo(string fileName)
        {
            lock (this)
            {
                if (fileName.Length > 0)
                {
                    _Bitmap = new Bitmap(fileName);

                    var r = new Rectangle(0, 0, _Bitmap.Width, _Bitmap.Height);
                    _bmdLogo = _Bitmap.LockBits(r, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
                }
                else
                {
                    if (_Bitmap != null)
                    {
                        _Bitmap.UnlockBits(_bmdLogo);

                        _Bitmap = null;
                        _bmdLogo = null;
                    }
                }
            }
        }


        /// <summary> build the capture graph for grabber. </summary>
        private void SetupGraph(DsDevice dev, int iFrameRate, int iWidth, int iHeight, string FileName)
        {
            ISampleGrabber sampGrabber = null;
            IBaseFilter baseGrabFlt = null;
            IBaseFilter capFilter = null;
            IBaseFilter muxFilter = null;
            IFileSinkFilter fileWriterFilter = null;
            ICaptureGraphBuilder2 capGraph = null;

            // Get the graphbuilder object
            filterGraph = new FilterGraph() as IFilterGraph2;
            mediaCtrl = filterGraph as IMediaControl;

#if DEBUG
            m_rot = new DsROTEntry(filterGraph);
#endif
            try
            {
                // Get the ICaptureGraphBuilder2
                capGraph = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();

                // Get the SampleGrabber interface
                sampGrabber = (ISampleGrabber)new SampleGrabber();

                // Start building the graph
                Try(capGraph.SetFiltergraph(filterGraph));
                // Add the video device
                Try(filterGraph.AddSourceFilterForMoniker(dev.Mon, null, dev.Name, out capFilter));
                baseGrabFlt = (IBaseFilter)sampGrabber;
                ConfigureSampleGrabber(sampGrabber);

                // Add the frame grabber to the graph
                Try(filterGraph.AddFilter(baseGrabFlt, "Ds.NET Grabber"));

                // If any of the default config items are set
                if (iFrameRate + iHeight + iWidth > 0)
                {
                    SetConfigParms(capGraph, capFilter, iFrameRate, iWidth, iHeight);
                }

                // Create a filter for the output avi file
                Try(capGraph.SetOutputFileName(MediaSubType.Avi, FileName, out muxFilter, out fileWriterFilter));
                // Connect everything together
                Try(capGraph.RenderStream(PinCategory.Capture, MediaType.Video, capFilter, baseGrabFlt, muxFilter));

                // Now that sizes are fixed, store the sizes
                SaveSizeInfo(sampGrabber);
            }
            finally
            {
                ReleaseComObjects(fileWriterFilter, muxFilter, capFilter, sampGrabber);
            }
        }
        public static void Try(int hResult)
        {
            DsError.ThrowExceptionForHR(hResult);
        }
        public static void ReleaseComObjects(params object[] objects)
        {
            foreach (var o in objects)
                ReleaseComObject(o);
        }
        public static void ReleaseComObject(object obj)
        {
            if (obj == null) return;
            Marshal.ReleaseComObject(obj);
            obj = null;
        }
        /// <summary> Read and store the properties </summary>
        private void SaveSizeInfo(ISampleGrabber sampGrabber)
        {
            // Get the media type from the SampleGrabber
            var media = new AMMediaType();
            Try(sampGrabber.GetConnectedMediaType(media));

            if ((media.formatType != FormatType.VideoInfo) || (media.formatPtr == IntPtr.Zero))
                throw new NotSupportedException("Unknown Grabber Media Format");

            // Grab the size info
            var videoInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(media.formatPtr, typeof(VideoInfoHeader));
            _videoWidth = videoInfoHeader.BmiHeader.Width;
            _videoHeight = videoInfoHeader.BmiHeader.Height;
            _stride = _videoWidth * (videoInfoHeader.BmiHeader.BitCount / 8);

            DsUtils.FreeAMMediaType(media);
            media = null;
        }
        /// <summary> Set the options on the sample grabber </summary>
        private void ConfigureSampleGrabber(ISampleGrabber sampGrabber)
        {
            var media =
                new AMMediaType
                    {
                        majorType = MediaType.Video,
                        subType = MediaSubType.RGB24,
                        formatType = FormatType.VideoInfo
                    };

            // Set the media type to Video/RBG24
            Try(sampGrabber.SetMediaType(media));

            DsUtils.FreeAMMediaType(media);
            media = null;

            // Configure the samplegrabber callback
            Try(sampGrabber.SetCallback(this, 1));
        }

        // Set the Framerate, and video size
        private void SetConfigParms(ICaptureGraphBuilder2 capGraph, IBaseFilter capFilter, int iFrameRate, int iWidth, int iHeight)
        {
            object o;
            AMMediaType media;
            IAMStreamConfig videoStreamConfig;
            IAMVideoControl videoControl = capFilter as IAMVideoControl;

            // Find the stream config interface
            Try(capGraph.FindInterface(
                PinCategory.Capture, MediaType.Video, capFilter, typeof(IAMStreamConfig).GUID, out o));

            videoStreamConfig = o as IAMStreamConfig;
            try
            {
                if (videoStreamConfig == null)
                {
                    throw new Exception("Failed to get IAMStreamConfig");
                }
                Try(videoStreamConfig.GetFormat(out media));

                // copy out the videoinfoheader
                var v = new VideoInfoHeader();
                Marshal.PtrToStructure(media.formatPtr, v);

                // if overriding the framerate, set the frame rate
                if (iFrameRate > 0)
                {
                    v.AvgTimePerFrame = 10000000 / iFrameRate;
                }

                // if overriding the width, set the width
                if (iWidth > 0)
                {
                    v.BmiHeader.Width = iWidth;
                }

                // if overriding the Height, set the Height
                if (iHeight > 0)
                {
                    v.BmiHeader.Height = iHeight;
                }

                // Copy the media structure back
                Marshal.StructureToPtr(v, media.formatPtr, false);

                // Set the new format
                Try(videoStreamConfig.SetFormat(media));

                DsUtils.FreeAMMediaType(media);
                media = null;

                // Fix upsidedown video
                if (videoControl != null)
                {
                    VideoControlFlags pCapsFlags;

                    var pPin = DsFindPin.ByCategory(capFilter, PinCategory.Capture, 0);
                    Try(videoControl.GetCaps(pPin, out pCapsFlags));

                    if ((pCapsFlags & VideoControlFlags.FlipVertical) > 0)
                    {
                        Try(videoControl.GetMode(pPin, out pCapsFlags));
                        Try(videoControl.SetMode(pPin, 0));
                    }
                }
            }
            finally
            {
                ReleaseComObject(videoStreamConfig);
            }
        }

        /// <summary> Shut down capture </summary>
        private void CloseInterfaces()
        {

            try
            {
                if (mediaCtrl != null)
                {
                    // Stop the graph
                    Try(mediaCtrl.Stop());
                    mediaCtrl = null;
                    _isRunning = false;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }

#if DEBUG
            if (m_rot != null)
            {
                m_rot.Dispose();
            }
#endif

            if (filterGraph != null)
            {
                Marshal.ReleaseComObject(filterGraph);
                filterGraph = null;
            }
            GC.Collect();
        }


        /// <summary> sample callback, NOT USED. </summary>
        int ISampleGrabberCB.SampleCB(double SampleTime, IMediaSample pSample)
        {
            ReleaseComObject(pSample);
            return 0;
        }

        /// <summary> buffer callback, COULD BE FROM FOREIGN THREAD. </summary>
        int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)
        {
            // Avoid the possibility that someone is calling SetLogo() at this instant
            lock (this)
            {
                if (_bmdLogo != null)
                {
                    var ipSource = _bmdLogo.Scan0;
                    var ipDest = pBuffer;

                    for (var x = 0; x < _bmdLogo.Height; x++)
                    {
                        CopyMemory(ipDest, ipSource, (uint)_bmdLogo.Stride);
                        ipDest = (IntPtr)(ipDest.ToInt32() + _stride);
                        ipSource = (IntPtr)(ipSource.ToInt32() + _bmdLogo.Stride);
                    }
                }
            }

            return 0;
        }
    }

}
